<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" >

<title>关于我自己搭建 .Net Core 半自动部署 | 没想好的个人博客</title>

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

<!-- <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous"> -->
   <!-- Font Awesome CSS-->
   <link href="https://cdn.bootcss.com/font-awesome/5.13.0/css/all.css" rel="stylesheet">

   <link rel="stylesheet" href="https://cdn.bootcss.com/font-awesome/5.7.2/css/all.min.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">

<link rel="shortcut icon" href="https://shixixiyue.gitee.io//favicon.ico?v=1709791195950">
<link rel="stylesheet" href="https://shixixiyue.gitee.io//styles/main.css">



<link rel="stylesheet" href="https://unpkg.com/aos@next/dist/aos.css" />
<script src="https://jsd.onmicrosoft.cn/npm/vue/dist/vue.js"></script>


<style>
  .hljs-ln{
    padding-bottom: 1.2rem;
    margin: 0rem !important;
  }
  .hljs-ln td {
    border: none !important;
    padding: 2px 6px !important;
    line-height: 1.2rem;
    font-size: 1rem;
  }
  .hljs-ln tr td:first-child  {
    color: cadetblue;
    border-right: 1px solid !important;
  }
  .hljs-ln tr {
    border: none !important;
  }
</style>

    <meta name="description" content="
实在是没有系统学习找工具，只能自己造，代替部分人工操作，实现提交代码后测试环境拉取测试，发布版本后正式环境拉取编译 发布。









环境
提交策略（略）
1. 服务器拉取的脚本
2. Gitea Web钩子配置
3. 触发脚本的..." />
    <meta name="keywords" content=".Net Core" />
  </head>
  <body>
    <div id="app" class="main">

      <div class="sidebar" :class="{ 'full-height': menuVisible }">
  <div class="top-container" data-aos="fade-right">
    <div class="top-header-container">
      <a class="site-title-container" href="https://shixixiyue.gitee.io/">
        <img src="https://shixixiyue.gitee.io//images/avatar.png?v=1709791195950" class="site-logo">
        <h1 class="site-title">没想好的个人博客</h1>
      </a>
      <div class="menu-btn" @click="menuVisible = !menuVisible">
        <div class="line"></div>
      </div>
    </div>
    <div>
      
        
          <a href="https://shixixiyue.gitee.io/" class="site-nav">
            首页
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/4gOKry8Qv/" class="site-nav">
            .Net Core
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/njETBpqXu/" class="site-nav">
            FineUICore
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/XyXyeLOBB" class="site-nav">
            读书笔记
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/TyVX6hfCJ" class="site-nav">
            Vue笔记
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/3Hz2sPtzO/" class="site-nav">
            产品
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tag/Db1wShB_E" class="site-nav">
            其他
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io/" class="site-nav">
            一一一一一一一一一
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//archives" class="site-nav">
            文章归档
          </a>
        
      
        
          <a href="https://shixixiyue.gitee.io//tags" class="site-nav">
            全部标签
          </a>
        
      
    </div>
  </div>
  <div class="bottom-container" data-aos="flip-up" data-aos-offset="0">
    <div class="social-container">
      
        
      
        
      
        
      
        
      
        
      
    </div>
    <div class="site-description">
      没事啥都说
    </div>
    <div class="site-footer">
      QQ：<a href="tencent://message/?uin=935732994&Site=''&Menu=yes">935732994<a/> <a href="http://beian.miit.gov.cn">京ICP备2024045283号<a/> | <a class="rss" href="https://shixixiyue.gitee.io//atom.xml" target="_blank">RSS</a>
    </div>
  </div>
</div>


      <div class="main-container">
        <div class="content-container" data-aos="fade-up">
          <div class="post-detail">
            <h2 class="post-title">关于我自己搭建 .Net Core 半自动部署</h2>
            <div class="post-date">2022-04-08
            
                
                  <a href="https://shixixiyue.gitee.io/tag/4gOKry8Qv/" class="tag">
                    .Net Core
                  </a>
                
            </div>
            
              <div class="feature-container" style="background-image: url('https://shixixiyue.gitee.io//post-images/guan-yu-wo-zi-ji-da-jian-net-core-ban-zi-dong-bu-shu.png')">
              </div>
            
            <div class="post-content" v-pre>
              <blockquote>
<p>实在是没有系统学习找工具，只能自己造，代替部分人工操作，实现提交代码后测试环境拉取测试，发布版本后正式环境拉取编译 发布。</p>
</blockquote>
<!-- more -->
<p><ul class="markdownIt-TOC">
<li>
<ul>
<li>
<ul>
<li>
<ul>
<li><a href="#%E7%8E%AF%E5%A2%83">环境</a></li>
<li><a href="#%E6%8F%90%E4%BA%A4%E7%AD%96%E7%95%A5%E7%95%A5">提交策略（略）</a></li>
<li><a href="#1-%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%8B%89%E5%8F%96%E7%9A%84%E8%84%9A%E6%9C%AC">1. 服务器拉取的脚本</a></li>
<li><a href="#2-gitea-web%E9%92%A9%E5%AD%90%E9%85%8D%E7%BD%AE">2. Gitea Web钩子配置</a></li>
<li><a href="#3-%E8%A7%A6%E5%8F%91%E8%84%9A%E6%9C%AC%E7%9A%84%E6%9C%8D%E5%8A%A1">3. 触发脚本的服务</a></li>
<li><a href="#4-%E6%8E%A8%E9%80%81%E5%88%B0%E6%AD%A3%E5%BC%8F%E6%9C%8D%E5%8A%A1%E5%99%A8">4. 推送到正式服务器</a></li>
<li><a href="#5-%E6%AD%A3%E5%BC%8F%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%9B%B4%E6%96%B0">5. 正式服务器更新</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</p>
<h4 id="环境">环境</h4>
<table>
<thead>
<tr>
<th>-</th>
<th>-</th>
</tr>
</thead>
<tbody>
<tr>
<td>代码环境</td>
<td>.Net Core 3.1</td>
</tr>
<tr>
<td>版本管理器</td>
<td>Gitea（Docker）<a href="https://www.cnblogs.com/guitu18/p/12817272.html">搭建</a></td>
</tr>
<tr>
<td>操作系统</td>
<td>Linux（以下服务器均为 Centos 7）</td>
</tr>
<tr>
<td>正式服务器IP</td>
<td>.15</td>
</tr>
<tr>
<td>镜像服务器IP<br>（作为正式服务镜像用于测试）</td>
<td>.38</td>
</tr>
<tr>
<td>测试服务器IP</td>
<td>.43</td>
</tr>
<tr>
<td>代码版本服务器IP</td>
<td>.44（就是Gitea所在服务）</td>
</tr>
</tbody>
</table>
<h4 id="提交策略略">提交策略（略）</h4>
<figure data-type="image" tabindex="1"><img src="https://shixixiyue.gitee.io//post-images/1684483423763.png" alt="提交策略" loading="lazy"></figure>
<h4 id="1-服务器拉取的脚本">1. 服务器拉取的脚本</h4>
<p>测试服务器中有一个脚本，从git上拉取最新代码（配置git过程略），然后将发布的代码覆盖，并启动服务。</p>
<p>这里我在VS中将项目发布，并一同提交，脚本直接拉取发布的文件夹进行本地覆盖，不在服务器上二次编译，但是 正式环境需要二次编译以保证编译通过才能发布。</p>
<p>该脚本在/home/gitpull.sh 下</p>
<p>注意 dev 为测试用代码分支</p>
<pre><code>echo 

mainpath=/home/MES #git路径 主程序路径
cd $mainpath
echo ----流水线开始获取代码----

#reset是重置 强制拉取的目的
git reset --hard origin/dev
git pull


cd $mainpath
project=/home/MES/02_Source/MES/MES/bin/Release/netcoreapp3.1/publish #项目路径 需要发布的路径
deploy=/home/MESpublish #发布后路径
dllname=MES.dll #应用名称


echo ------执行结束命令-----
cd $deploy
./ShutDown.sh

echo ------复制发布的文件-----
cp -rf $project $deploy


echo ------执行start----
./Start.sh


echo ------结束------

</code></pre>
<p>ShutDown.sh 代码 用于停止.net core 项目</p>
<pre><code>port=80 #项目端口号
#根据端口号查询对应的pid
pid=$(netstat -nlp | grep -w :$port  | awk '{print $7}' | awk -F&quot;/&quot; '{ print $1 }');
#杀掉对应的进程，如果pid不存在，则不执行
if [ -n  &quot;$pid&quot; ];  then
    kill -9 $pid;
fi
</code></pre>
<p>Start.sh 代码 用于启动项目</p>
<pre><code>#!/bin/sh
#启动MVC
#先到目录下 否则报错
cd /home/MESpublish/publish
#不显示日志
nohup dotnet HD.MES.dll &gt;/dev/null 2&gt;&amp;1&amp;
~                                              
</code></pre>
<p><code>如果不cd到发布路径虽然会启动项目，但是访问会报错！原因未知</code></p>
<p>在发布后手动执行这个脚本，就可以愉快的测试了</p>
<figure data-type="image" tabindex="2"><img src="https://shixixiyue.gitee.io//post-images/1684483450424.png" alt="拉取" loading="lazy"></figure>
<h4 id="2-gitea-web钩子配置">2. Gitea Web钩子配置</h4>
<blockquote>
<p>这里的钩子就是当Git触发动作时向指定的Web服务推送一段JSON，动作包括 拉取 推送 发布 分支 等，JSON中包含项目信息 提交人 版本信息 等。</p>
</blockquote>
<p>这里我主要配置了发布钩子</p>
<figure data-type="image" tabindex="3"><img src="https://shixixiyue.gitee.io//post-images/1684483461957.png" alt="钩子配置" loading="lazy"></figure>
<figure data-type="image" tabindex="4"><img src="https://shixixiyue.gitee.io//post-images/1684483465857.png" alt="发布配置" loading="lazy"></figure>
<p>保存后发送的内容</p>
<figure data-type="image" tabindex="5"><img src="https://shixixiyue.gitee.io//post-images/1684483482170.png" alt="保存后发送的内容" loading="lazy"></figure>
<p>可以看到我的 测试服务器上有一个服务8070 ，是一个自己写的WebAPI服务，核心代码如下:</p>
<h4 id="3-触发脚本的服务">3. 触发脚本的服务</h4>
<blockquote>
<p>该服务主要接收发布的事件，触发sh脚本（/home/MESReleases/pullPublish.sh）</p>
</blockquote>
<pre><code>using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace GitWebApiTest.Controllers
{
    [Route(&quot;api/[controller]&quot;)]
    [ApiController]
    public class GiteaController : ControllerBase
    {
        [HttpPost]
        public ActionResult&lt;string&gt; Post(dynamic _data)
        {
            dynamic data = JsonConvert.DeserializeObject(Convert.ToString(_data));
            Task.Run(() =&gt;
            {
                string path = data[&quot;repository&quot;][&quot;name&quot;].ToString();
                if (path == &quot;项目名称&quot;)
                {
                    try
                    {
                        //Console.WriteLine(data);
                        var tag_name = data[&quot;release&quot;][&quot;tag_name&quot;].ToString();

                        JArray assets = JArray.FromObject(data[&quot;release&quot;][&quot;assets&quot;]);
                        var urls = assets.AsJEnumerable().Select(x =&gt; x).ToArray()
                        .Select(x =&gt;
                            $&quot;{x[&quot;browser_download_url&quot;]}|{x[&quot;name&quot;]}&quot;
                          );

                        var prerelease = data[&quot;release&quot;][&quot;prerelease&quot;].ToString() == &quot;True&quot; ? &quot;1&quot; : &quot;0&quot;;
                        //Console.WriteLine(string.Join(&quot;,&quot;, urls));

                        if (prerelease == &quot;true&quot;) { return; }//拦截预览版 稳定版才执行

                        //创建一个ProcessStartInfo对象 使用系统shell 指定命令和参数 设置标准输出
                        var sh = $&quot;/home/MESReleases/pullPublish.sh {tag_name} {prerelease} {string.Join(&quot;,&quot;, urls)}&quot;;
                        Console.WriteLine(sh);

                        var psi = new ProcessStartInfo(&quot;sh&quot;, sh)
                        {
                            RedirectStandardOutput = true
                        };
                        //启动
                        var proc = Process.Start(psi);
                        if (proc == null)
                        {
                            Console.WriteLine(&quot;Can not exec.&quot;);
                        }
                        else
                        {
                            Console.WriteLine(&quot;-------------Start read standard output--------------&quot;);
                            //开始读取
                            using (var sr = proc.StandardOutput)
                            {
                                while (!sr.EndOfStream)
                                {
                                    Console.WriteLine(sr.ReadLine());
                                }

                                if (!proc.HasExited)
                                {
                                    proc.Kill();
                                }
                            }
                        }
                    }
                    catch (Exception ex) { }
                }
            });
            //}
            return &quot;OK&quot;;
        }
    }
}
</code></pre>
<h4 id="4-推送到正式服务器">4. 推送到正式服务器</h4>
<p>pullPublish.sh 脚本 用于将发布后的源码下载，解压，编译，打包（压缩），发送到正式服务器的指定目录，这样我在发布时点击保存，正式服务器就有包了</p>
<p>代码较多， 这里进行拆解</p>
<p>4.1 判断参数</p>
<p>脚本接收三个参数：</p>
<ul>
<li>版本号，必须 用于创建目录，拼接下载地址</li>
<li>是否是预览版，必须 判断代码包是否发送到正式服务器（预览版不发送）</li>
<li>附件地址，非必须 以逗号分割</li>
</ul>
<pre><code>if [ 2 -gt $# ]
   then
     echo  &quot;缺少参数：第1个参数是版本号，第2个参数 1 预览版 0 稳定版&quot;
     exit;
fi
</code></pre>
<p>4.2 创建发布目录和log目录</p>
<p>也可以不赋权限</p>
<pre><code>mainpath=/home/MESReleases/$1
if [ ! -d $mainpath  ];then
    mkdir $mainpath
    chmod 777 $mainpath
fi
Logpath=/home/MESReleases/log`date '+%Y%m%d'`.log
echo &quot; &quot; &gt;&gt; $Logpath
echo &quot;———————$1————————–&quot; &gt;&gt; $Logpath
</code></pre>
<p>4.3 下载到刚才建立的文件夹</p>
<p>之间将版本号 $1 拼接到下载的地址</p>
<p>使用 wget 进行下载 -P 表示指定目录</p>
<pre><code>#下载
wgeturl=http://192.168.123.44:9000/……/archive/$1.tar.gz
echo &quot;开始下载&quot; $wgeturl &gt;&gt; $Logpath
echo &quot;开始下载&quot; $wgeturl
wget -N $wgeturl -P $mainpath
</code></pre>
<p>下载文件超过1G会很慢，改为通过Git命令获取源码，git会自动检测更新项不用每次都下载 代码如下</p>
<p>在指定目录下载源码，执行一次就行</p>
<pre><code> cd /home/MESReleases/Source/
 git init
 git config --local core.sparsecheckout true
 git remote add origin http://192.168.123.44:9000/XXXX
 #只获取指定目录 即代码所在目录
 echo '/02_Source/' &gt;&gt; .git/info/sparse-checkout
 git pull origin master
</code></pre>
<p>将下载代码替换为获取</p>
<pre><code>//到源码目录下
cd /home/MESReleases/Source/
#重置
git fetch
#拉取
git reset --hard origin/master
git pull

#如果有先删除
if [ -f $1/02_Source ]; then
 rm -rf $1/02_Source
fi
#复制 注意-r的使用，没有提示 cp 略过目录
cp -r 02_Source $1/02_Source

#此代码会让代码获取速度提示一半，并且留存了版本代码，但是只会获取最新的代码，对于发布最新代码也可以
</code></pre>
<p>4.4 解压</p>
<p>--exclude 是跳过文件的意思<br>
--strip-components 是跳级的意思</p>
<pre><code>#解压

echo &quot;开始解压到&quot; $mainpath &gt;&gt; $Logpath
echo &quot;开始解压到&quot; $mainpath
tar -zxf $mainpath/$1.tar.gz --exclude=/hd_mes/01_设计书说明文档 --exclude=/hd_mes/03_其他资料 -C $mainpath hd_mes/02_Source/MES/ --strip-components 1

</code></pre>
<p>4.5 编译发布</p>
<p>-nowarn 表示不弹出编号为XXX的提示<br>
-o 发布到指定目录</p>
<pre><code>echo &quot;开始发布&quot; $project &gt;&gt; $Logpath
echo '开始发布' $project

project=$mainpath/02_Source/…….csproj
publish=&quot;$mainpath/publish&quot;
if [ ! -d $publish  ];then
    mkdir $publish
    chmod 777 $publish
fi

#增加本地变量，指定执行目录，dotnet在后台执行时会失去运行上下文导致报错
export DOTNET_CLI_HOME=/home

dotnet publish $project -c Release -o $publish --self-contained false -nowarn:msb3202,nu1503,cs1591,cs0168,cs1998,cs4014,cs0219,cs0472,MSB3277,CS8632,CS0108,CS0162,CS8321,CS1570,CS1587
echo &quot;发布成功&quot; &gt;&gt; $Logpath
echo '发布成功'
</code></pre>
<p>4.6 打包压缩</p>
<p>注意这里也是 cd 之后再压缩，否则会按路径压缩</p>
<pre><code>echo &quot;开始打包&quot; &gt;&gt; $Logpath
echo '开始打包'
file=$1.tar.gz
cd $mainpath
tar czf $mainpath/$file ./publish
echo &quot;打包完成&quot; &gt;&gt; $Logpath
echo '打包完成'
</code></pre>
<p>4.7 发送文件</p>
<p>rsync -a 和 scp 的效果是一样的，发送到指定服务器的指定目录</p>
<p>向服务器发送 需要两个服务器<a href="https://blog.csdn.net/u013415591/article/details/81943189">设置免密</a></p>
<pre><code>mainserver=root@192.168.2.15
imageserver=root@192.168.123.38
if [ $2 -eq 0 ];then
    echo &quot;发送&quot; $mainpath/$file &gt;&gt; $Logpath
    echo '发送' $mainpath/$file
    ssh $mainserver &quot;mkdir $mainpath&quot;
    ssh $imageserver &quot;mkdir $mainpath&quot;
    rsync -a $mainpath/$file $mainserver:$mainpath
    scp $mainpath/$file $imageserver:$mainpath
fi
</code></pre>
<p>4.8 附件的下载与发送</p>
<pre><code>echo &quot;得到附件&quot; $3  &gt;&gt; $Logpath
if [ -n &quot;$3&quot; ];then
    assetpath=$mainpath/assets
    if [ ! -d $assetpath  ];then
        mkdir $assetpath
        chmod 777 $assetpath
    fi
    assets=$3
    assets=${assets//,/ }
    for element in $assets
    do
        asseturl=`echo $element | cut -d '|' -f 1`
        assetname=`echo $element | cut -d '|' -f 2`
        echo &quot;下载附件&quot; $asseturl &quot;|&quot; $assetname &gt;&gt; $Logpath
        wget -N $asseturl -O $assetpath/$assetname -P $assetpath
    done
    ssh $mainserver &quot;mkdir $mainpath&quot;
    ssh $mainserver &quot;mkdir $assetpath&quot;

    ssh $imageserver &quot;mkdir $mainpath&quot;
    ssh $imageserver &quot;mkdir $assetpath&quot;

    echo &quot;发送文件&quot; $assetpath &gt;&gt; $Logpath
    scp -r $assetpath $mainserver:$mainpath
    scp -r $assetpath $imageserver:$mainpath
fi
</code></pre>
<figure data-type="image" tabindex="6"><img src="https://shixixiyue.gitee.io//post-images/1684483556083.png" alt="发布" loading="lazy"></figure>
<h4 id="5-正式服务器更新">5. 正式服务器更新</h4>
<p>更新需要经历：</p>
<ul>
<li>备份当前版本</li>
<li>解压新版本 覆盖</li>
<li>关闭服务</li>
<li>开启服务</li>
</ul>
<p>得到当前版本我直接在项目中 增加了API 获取版本</p>
<pre><code>version=$( curl -s http://localhost/api/Version| awk '{print $0}')
</code></pre>
<p>具体脚本解释参照上面</p>
<pre><code>#启动
#!/bin/bash
if [ 1 -gt $# ]
   then
     echo  &quot;缺少参数：第1个参数是版本号&quot;
     exit;
fi
mainpath=/home/MESReleases/$1
if [ ! -f $mainpath/$1.tar.gz  ];then
     echo  &quot;没有版本文件 $mainpath/$1.tar.gz 退出&quot;
     exit;
fi
Logpath=/home/MESReleases/log`date '+%Y%m%d'`.log
echo &quot; &quot; &gt;&gt; $Logpath
echo &quot;———————发布 $1————————–&quot; &gt;&gt; $Logpath

version=$( curl -s http://localhost/api/Version| awk '{print $0}')
echo &quot;得到版本信息 $version&quot; &gt;&gt; $Logpath
echo &quot;得到版本信息 $version&quot; 
echo &quot;备份 $version&quot; &gt;&gt; $Logpath
echo &quot;备份 $version&quot; 
/home/MESBackup.sh $version
echo &quot;停止 $version&quot; &gt;&gt; $Logpath
echo &quot;停止 $version&quot; 
/home/MESpublish/Shutdown.sh
echo &quot;更新文件 $1&quot; &gt;&gt; $Logpath
echo &quot;更新文件 $1&quot; 
tar -zxf $mainpath/$1.tar.gz  --exclude=publish/log --exclude=publish/appsettings.json -C /home/MESpublish/
echo &quot;启动 $1&quot; &gt;&gt; $Logpath
echo &quot;启动 $1&quot; 
</code></pre>
<figure data-type="image" tabindex="7"><img src="https://shixixiyue.gitee.io//post-images/1684483505267.png" alt="更新" loading="lazy"></figure>
<hr>
<p>之后会结合自动测试命令，让发布更<code>敏捷</code></p>
<pre><code>dotnet test --configuration release --logger:&quot;trx;LogFilePrefix=testResults&quot; -v normal -noconlog
</code></pre>

            </div>
            
              <div class="tag-container">
                
                  <a href="https://shixixiyue.gitee.io/tag/4gOKry8Qv/" class="tag">
                    .Net Core
                  </a>
                
              </div>
            
            
              <div class="next-post">
                <div class="next">下一篇</div>
                <a href="https://shixixiyue.gitee.io/2022031601/">
                  <h3 class="post-title">
                    Windows终端工具-Tabby Terminal
                  </h3>
                </a>
              </div>
            

            

          </div>

        </div>
      </div>
    </div>

    <script src="https://unpkg.com/aos@next/dist/aos.js"></script>
<script type="application/javascript">

AOS.init();

var app = new Vue({
  el: '#app',
  data: {
    menuVisible: false,
  },
})

</script>


  <script src="//jsd.onmicrosoft.cn/gh/highlightjs/cdn-release@11.5.1/build/highlight.min.js"></script>
  <script>
    hljs.initHighlightingOnLoad();
    //hljs.registerLanguage('mermaid', window.hljsDefineMermaid);
  </script>
  <script src="//jsd.onmicrosoft.cn/npm/highlightjs-line-numbers.js@2.8.0/dist/highlightjs-line-numbers.min.js"></script>
  <script>
    hljs.initLineNumbersOnLoad({
      singleLine: true
    });
  </script>
 <script src="https://jsd.onmicrosoft.cn/npm/mermaid/dist/mermaid.min.js"></script>
   <script>
    mermaid.initialize({
      startOnLoad: true,
      securityLevel: 'loose',
    });
  </script>





  </body>
</html>
